package hotel.dao; 

import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

import org.apache.log4j.Logger;

import hotel.dao.base.ConnectionBehavior;
import hotel.dao.base.ConnectionProvider;
import hotel.dao.base.DaoCommand;
import hotel.dao.impl.RequestDaoImpl;
import hotel.dao.impl.RoomDaoImpl;
import hotel.dao.impl.UserDaoImpl;
import hotel.data.ConnectionByDriverManager;
import hotel.exceptions.NoConnectionFoundException;

public class DaoManager 
{
	private static final Logger logger = Logger.getLogger(DaoManager.class);
	private static final String DAOMANAGER_ERROR = "Error in DaoManager:";


	@SuppressWarnings("rawtypes")
	private Map<Class, Object> daoObjects;
	private Connection connection;
	private ConnectionProvider provider;
	
	@SuppressWarnings("rawtypes")
	private DaoManager(ConnectionProvider provider) 
	{
		super();
		this.provider = provider;
		this.daoObjects = new HashMap<Class, Object>();
	}
	
	private void refreshState() 
	{
		try 
		{
			if (getConnection() == null || getConnection().isClosed()) 
			{
				
				this.connection = this.provider.getConnection();

				daoObjects.put(UserDao.class, new UserDaoImpl(getConnection()));
				daoObjects.put(RoomDao.class, new RoomDaoImpl(getConnection()));
				daoObjects.put(RequestDao.class, new RequestDaoImpl(getConnection()));
			}
		} 
		catch (SQLException e) 
		{
			this.connection = provider.getConnection();
		}
	}
	
	private Connection getConnection()
	{
		return this.connection;
	}

/**
 * This method create new connection use ConnectionByDriverManager
 * @return
 */
	public static DaoManager getDefault() 
	{
		return
			new DaoManager(new ConnectionProvider() 
				{
					@Override
					public Connection getConnection() 
					{
						try 
						{
							return ConnectionByDriverManager.getConnection();
						} 
						catch (NoConnectionFoundException e) 
						{
							logger.error(DAOMANAGER_ERROR + e);
							throw new RuntimeException(e);	
						}
					
					}
				});
	}
	
	public Object transaction(DaoCommand command, ConnectionBehavior behavior) 
	{
		try 
		{
			this.refreshState();
			getConnection().setAutoCommit(false);
			Object returnValue = command.execute(this);
			getConnection().commit();
			
			return returnValue;
		} 
		catch(Exception e) 
		{
			
			try 
			{
				getConnection().rollback();
			}
			catch (SQLException e1) 
			{
				throw new RuntimeException(e1);
			}
			
			throw new RuntimeException(e);
		} 
		finally 
		{
			
			try 
			{
				getConnection().setAutoCommit(true);
				// close connection if needed
				if (ConnectionBehavior.CLOSE.equals(behavior)) 
				{
					getConnection().close();
				}
			}
			catch (SQLException e) 
			{
				throw new RuntimeException(e);
			}
		}
	}
	
	public Object command(DaoCommand command, ConnectionBehavior behavior) {
		
		this.refreshState();
		Object result = command.execute(this);

		if (ConnectionBehavior.CLOSE.equals(behavior)) {
			try {
				getConnection().close();
			} catch (SQLException e) {
				throw new RuntimeException(e);
			}
		}
		return result;
	}

	public <T> T getDao(Class<?> daoInterface) {
		@SuppressWarnings("unchecked")
		final T dao = (T) daoObjects.get(daoInterface);
		
		if (dao == null) {
			throw new IllegalArgumentException("Unsupported DAO type:" + daoInterface.getClass().getName());
		}
		
		return dao;
	}
}
